

package com.yeyks.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import com.google.common.collect.Lists;
import com.yeyks.account.dal.domain.AccountInfo;
import com.yeyks.account.dal.domain.SysLog;
import com.yeyks.account.service.SysLogService;
import com.yeyks.annotation.SystemLog;
import com.yeyks.common.utils.HttpContextUtils;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;

/**
 * 系统日志，切面处理类
 *
 * @author shuai
 */
@Aspect
@Component
public class SystemLogAspect {
    @Autowired
    private SysLogService sysLogService;

    private static List<String> types = Lists.newArrayList("java.lang.Integer", "java.lang.Double",
            "java.lang.Float", "java.lang.Long", "java.lang.Short",
            "java.lang.Byte", "java.lang.Boolean", "java.lang.Char",
            "java.lang.String", "int", "double", "long", "short", "byte",
            "boolean", "char", "float");

    /**
     * 设置切点，只切带SysLog注解的
     */
    @Pointcut("@annotation(com.yeyks.annotation.SystemLog)")
    public void logPointCut() {

    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //方法执行完后进行日志的写入
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //保存日志
        saveSysLog(point, time);
        return result;
    }

    /**
     * 保存需要的日志信息
     *
     * @param joinPoint
     * @param time
     */
    private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        //获取请求的方法对象
        Method method = signature.getMethod();
        //获取请求的方法名
        String methodName = signature.getName();
        //获取参数名
        String[] paramNames = getFieldsName(this.getClass(), className, methodName);
        //获取所有参数拼接
        String paramContents = writeLogInfo(paramNames, joinPoint);
        SysLog sysLog = new SysLog();
        sysLog.setMethod(className + "." + methodName + "()");
        SystemLog systemLog = method.getAnnotation(SystemLog.class);
        if (null != systemLog) {
            //注解上的描述
            sysLog.setOperation(systemLog.value());
        }
        //请求的参数
        sysLog.setParams(paramContents);
        //获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        //设置IP地址
        sysLog.setIp(ServletUtil.getClientIP(request));
        AccountInfo loginAccount = (AccountInfo) request.getAttribute("loginUser");
        if (null != loginAccount) {
            //用户名
            sysLog.setUsername(loginAccount.getUsername());
            sysLog.setTime(time);
            sysLog.setCreateDate(new Date());
            //保存系统日志
            sysLogService.save(sysLog);
        }
    }

    /**
     * 得到方法参数的名称
     *
     * @param cls
     * @param className
     * @param methodName
     * @return
     */
    private static String[] getFieldsName(Class cls, String className, String methodName) {
        try {
            ClassPool pool = ClassPool.getDefault();
            ClassClassPath classPath = new ClassClassPath(cls);
            pool.insertClassPath(classPath);
            CtClass cc = pool.get(className);
            CtMethod cm = cc.getDeclaredMethod(methodName);
            MethodInfo methodInfo = cm.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            if (attr != null) {
                String[] paramNames = new String[cm.getParameterTypes().length];
                int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
                for (int i = 0; i < paramNames.length; i++) {
                    //paramNames即参数名
                    paramNames[i] = attr.variableName(i + pos);
                }
                return paramNames;
            }
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取需要记录的参数
     *
     * @param paramNames
     * @param joinPoint
     * @return
     */
    private static String writeLogInfo(String[] paramNames, JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        for (int k = 0; k < args.length; k++) {
            //过滤不需要写入的日志
            if ("loginUser".equals(paramNames[k]) || "token".equals(paramNames[k]) || "accountInfo".equals(paramNames[k])) {
                continue;
            }
            Object arg = args[k];
            if (null != arg) {
                // 获取对象类型
                String typeName = arg.getClass().getTypeName();
                if (types.contains(typeName)) {
                    sb.append(paramNames[k]).append("=").append(arg).append("; ");
                }
                sb.append(getFieldsValue(arg));
            }
        }
        return sb.toString();
    }

    /**
     * 得到参数的值
     *
     * @param obj
     */
    private static String getFieldsValue(Object obj) {
        Field[] fields = obj.getClass().getDeclaredFields();
        String typeName = obj.getClass().getTypeName();
        if (types.contains(typeName)) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (Field f : fields) {
            f.setAccessible(true);
            try {
                if (types.contains(f.getType().getName()) && !"serialVersionUID".equals(f.getName())) {
                    sb.append(f.getName()).append(" = ").append(f.get(obj)).append("; ");
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }
}
